/**
 *
 */
package org.jprobe.junit;

import java.lang.reflect.Constructor;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.jprobe.reflect.classpath.AbstractClassPath;

/**
 * implementation of JUnitTestUnitBlockInterface
 *
 * @author Hussain Al-Mutawa
 * @version 1.0
 *
 */
public class JUnitTestUnitBlock<T extends GenericDeclaration & Member> implements JUnitTestUnitBlockInterface<T>,Comparable<JUnitTestUnitBlockInterface<T>> {
	/** the testing member (field/method/constructor) associated with the test unit **/
	private transient T member;
	/** the test block type which can be
	 *    <code>{@link JUnitTestUnitBlockInterface#GETTER}</code>
	 * or <code>{@link JUnitTestUnitBlockInterface#GETTER}</code>
	 **/
	private transient int testBlockType;
	/** the timeout specified to make the test case fail if passed **/
	private transient int timout;
	/** the annotation associated with the test unit **/
	private transient String annotation=DEFAULT_ANNOTATION;
	/** the expected exception thrown in the annotation **/
	private transient String expectedException = NO_EXCEPTION;
	/** list of the exception thrown by executing the test member as declared in the member definition header **/
	private transient List<String>exceptionsThrown;
	/** the body of the test unit **/
	private transient String testingBody;
	/** the unit test block documentation comments **/
	private transient String documentation = DEFAULT_DOCUMENTATION;
	/**
	 * initializes new JUnitTestUnitBlock
	 * and prepare its attributes
	 */
	public JUnitTestUnitBlock() {
		super();
		this.exceptionsThrown=new LinkedList<String>();
		this.setTimeout(DEFAULT_TIMEOUT);
		this.testingBody = "";
	}

	/**
	 * initializes new JUnitTestUnitBlock
	 * and prepare its attributes
	 * @param member is the method/constructor to be tested
	 */
	public JUnitTestUnitBlock(final T member) {
		this();
		this.setTestingMember(member);
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#getTestBody()}
	 */
	@Override
	public String getTestBody() {
		return this.testingBody;
	}
	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#setTestBody(java.lang.String)}
	 */
	@Override
	public final void setTestingBody(final String testingBody) {
		this.testingBody=testingBody;
	}


	/**
	 * provides comparability of the unit test block in order
	 * to be sorted in the list
	 */
	@Override
	public int compareTo(final JUnitTestUnitBlockInterface<T> other) {
		return other.getUnitTestCaseName().compareTo(this.getUnitTestCaseName());
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#getExpectedException()}
	 */
	@Override
	public String getExpectedException() {
		return this.expectedException;
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#setExpectedExceptionClass(String)}
	 */
	@Override
	public void setExpectedExceptionClass(final String expectedExceptionClass) {
		this.expectedException=expectedExceptionClass;
		updateAnnotation();
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#getAnnotation()}
	 */
	@Override
	public String getAnnotation() {
		return this.annotation;
	}

	@Override
	public T getMember() {
		return this.member;
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#getThrowsExceptionClasses()}
	 */
	@Override
	public String[] getThrowsExceptionClasses() {
		return exceptionsThrown.toArray(new String[this.exceptionsThrown.size()]);
	}



	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#getTimeout()}
	 */
	@Override
	public int getTimeout() {
		return this.timout;
	}

	/**
	 * see {@link org.jprobe.junit.JUnitTestUnitBlockInterface#setTimeout()}
	 */
	@Override
	public final void setTimeout(final int timeout) {
		this.timout=timeout;
		this.updateAnnotation();
	}

	/**
	 * updates the annotation declaration, this method has to be called
	 * after calling either setTimeout() or setExpectedExceptionClass()
	 */
	private final void updateAnnotation() {
		String expected;
		if(NO_EXCEPTION.equals(this.getExpectedException())){
			expected=NO_EXCEPTION;
		}else{
			expected="expected="+this.getExpectedException()+".class,";
		}
		this.annotation=DEFAULT_ANNOTATION+"("+expected+"timeout="+this.getTimeout()+")";
	}


	@Override
	public int getUnitTestBlockType() {
		return this.testBlockType;
	}

	@Override
	public String getUnitTestCaseName() {
		return "test"+(this.member.getName().charAt(0)+"").toUpperCase()+this.member.getName().substring(1);
	}

	@Override
	public final void setTestingMember(final T member) {
		this.member=member;
		final Class<?>[] exceptions = getExceptionTypes(member);
		this.exceptionsThrown.clear();
		for(final Class<?>exception:exceptions){
			this.exceptionsThrown.add(AbstractClassPath.getClassBinaryName(exception));
		}
		if(member instanceof Constructor<?> || (((Method)member).getReturnType() == Void.TYPE)){
			this.testBlockType=SETTER;
		}
	}

	/**
	 * gets the exception thrown by calling the member (method/constructor)
	 * of this unit test block
	 * @return list of exception thrown as string
	 */
	public String getThrowsExceptionsDeclaration(){
		String result = Arrays.toString(this.exceptionsThrown.toArray()).replace("[", "").replace("]", "").trim();
		if(!"".equals(result)){
			result = " throws "+result;
		}
		return result;
	}

	/**
	 * gets exception types thrown by the member used in the test block
	 * the exceptions are those declared in the method/constructor as to be
	 * thrown in case unforeseen situation has happened.
	 *
	 * @param member a method or constructor
	 * @return list of exceptions types
	 */
	public Class<?>[] getExceptionTypes(final T member) {
		final Class<?>[]result;
		if(member instanceof Method){
			result = ((Method)member).getExceptionTypes();
		}else{
			result = ((Constructor<?>)member).getExceptionTypes();
		}
		return result;
	}

	@Override
	public String getDocumentation() {
		return documentation;
	}


	@Override
	public void setDocumentation(String documentation) {
		this.documentation=documentation;
	}
	/**
	 * gets the whole block as a unit test case java code,
	 * an example would be<pre><span style='color:#0066CC'>
	 * &#47;**
	 *  * test case for {SomeClass.isOK()}
	 *  **&#47;
	 * </span><span style='color:grey'>&#64;Test</span><span style='color:green'>(<span style='color:red'>[expected=Exception.class,]</span>timeout=1000)</span>
	 * <strong style='color:#660099'>public void</strong> testSomething() <span style='color:red'>[throws Exception]</span>{
	 * &#09;final SomeClass obj = new SomeClass();
	 * &#09;org.junit.Assert.assertTrue(obj.isOK());
	 * }
	 * </pre>
	 * @return unit test block code
	 */
	@Override
	public String toString() {
		return 	this.getDocumentation() + '\n' +
				this.getAnnotation()+ '\n'+
				"public void " + this.getUnitTestCaseName() + "()" +
				this.getThrowsExceptionsDeclaration()+" throws Exception{\n"+
				this.getTestBody()+"\n}";
	}
}
